//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_PULSE_EVENT_H
#	define NJ_PULSE_EVENT_H

#	include "NjNinjaSyncLib.h"
#	include "NjLocker.h"
#	include "NjEventsSystem.h"
#	include "NjPulseEvent_config.h"
#	include "NjConfiguration.h"

	/// \brief Implements a auto-reset reset event
	class NjPulseEvent
	{
	public:
		/// \brief Defines the timeout type
		typedef NjEventsSystem::TimeMs TimeMs;

		/// \brief Creates a new NjPulseEvent instance
		NjPulseEvent();

		/// \brief Destroys this NjPulseEvent instance
		~NjPulseEvent();

		/// \brief Aakes one of the threads waiting on this event
		///
		/// If no threads are currently waiting, nothing happens
		void WakeOne();

		/// \brief Aakes all the threads waiting on this event
		void WakeAll();

		/// \brief The calling thread waits indefinitely until another thread wakes it calling WakeOne or WakeAll
		void Wait();

		/// \brief The calling thread waits for \em uTimeMs milliseconds maximum until another thread wakes it calling WakeOne or WakeAll
		///
		/// If the thread is waken within the \em uTimeMs milliseconds, it will return NJ_TRUE.
		/// Otherwise, it will return NJ_FALSE.
		/// TryWait(0) will return immediately NJ_FALSE.
		/// TryWait(NjEventsSystem::WAIT_FOREVER` is identical to Wait
		NjBool TryWait(TimeMs uTimeMs);

	private:
		/// \brief Decreases the WAITING_THREADS bitfield
		///
		/// If there are more waken threads than waiting threads, it also decreases the WAKEN_THREADS bitfield
		void RemoveWaitingThread();

		/// \brief The kernel event type
		typedef NjEventsSystem::Event Event;

		/// \brief Used to store the state of the kernel event object
		///
		/// This bit can be set only if the kernel object is allocated.
		/// When this bit is set it is expected that NjEventsSystem::TryWaitEvent operations will block the calling thread
		static const NjUint STATE_EVENT_UNSET          = 0x00000001;

		/// \brief Counts the waiting threads
		static const NjUint STATE_WAITING_THREADS_ONE  = 0x00000002;

		/// \var static const NjUint STATE_WAITING_THREADS_MASK
		/// \brief The mask used to define the maximum number of waiting threadsS

#if NJ_PLATFORM_64_BIT
		static const NjUint STATE_WAITING_THREADS_MASK = 0x00000000003FFFFEULL;
#else // NJ_PLATFORM_64_BIT
		static const NjUint STATE_WAITING_THREADS_MASK = 0x000007FE;
#endif // NJ_PLATFORM_64_BIT

		/// \brief Counts the threads that should be waken
		static const NjUint STATE_WAKEN_THREADS_ONE    = STATE_WAITING_THREADS_MASK+STATE_WAITING_THREADS_ONE;

		/// \brief The mask used to define the maximum number of threads that can be waken
		static const NjUint STATE_WAKEN_THREADS_MASK   = STATE_WAITING_THREADS_MASK*(STATE_WAKEN_THREADS_ONE/STATE_WAITING_THREADS_ONE);

		/// \brief A tag counter used to avoid the ABA problem
		/// \sa http://en.wikipedia.org/wiki/ABA_problem
		static const NjUint STATE_ABA_COUNTER_ONE      = STATE_WAKEN_THREADS_MASK+STATE_WAKEN_THREADS_ONE;

		/// \brief The current state of the pulse event
		NjAtomic<Event *> m_kEvent;

		/// \brief The kernel event assigned to this pulse event
		NjAtomic<NjUint> m_kState;

#	if NJ_PULSE_EVENT_STORE_USERS_ENABLED
		/// \brief Called immediately entering into a method
		void UserEnter();

		/// \brief Called before leaving a method
		void UserLeave();

		/// \brief This counter stores the current threads accessing to this resource
		NjAtomic<NjUint> m_kUsers;
#	endif // NJ_PULSE_EVENT_STORE_USERS_ENABLED

#	if NJ_PULSE_EVENT_TRACKING_ENABLED
		/// \brief Class to track pulse events usage
		class TrackerEntry;

		/// \copydoc TrackerEntry
		class Tracker;
#	endif // NJ_PULSE_EVENT_TRACKING_ENABLED

		NJ_DISABLE_COPY(NjPulseEvent);
	};

#	include "NjPulseEvent_inline.h"

#endif // NJ_PULSE_EVENT_H
